package simulation;

import java.awt.Dimension;
import java.util.HashMap;
import java.util.List;

import util.Location;
import util.Vector;
import forces.*;

/**
 * Class containing the global environment variables for use in the simulation.
 * These are not changed after the initial setup, but are accessed by
 *      the mass class.
 *
 * @author Schwartz & Roy
 */
public class Environment implements EnvironmentConstants{

    // forces information
    private HashMap<String, Force> forces = new HashMap<String, Force>();

    /**
     * constructor sets default values for Environment.
     */
    public Environment() {
        // initializes default values
        forces = setDefaultForces();
    }

    /**
     * helper method to initialized reasonable default values
     *      of forces.
     * @return a HashMap of created name mapped to Force object
     */
    private HashMap<String, Force> setDefaultForces() {
        HashMap<String, Force> f = new HashMap<String, Force>();
        f.put(GRAVITY_NAME, new GravityForce());
        f.put(VISCOSITY_NAME, new Viscosity());
        f.put(CENTERMASS_NAME, new CenterOfMassForce());
        f.put(TOPWALL_NAME, new TopWallForce());
        f.put(RIGHTWALL_NAME, new RightWallForce());
        f.put(BOTTOMWALL_NAME, new BottomWallForce());
        f.put(LEFTWALL_NAME, new LeftWallForce());

        return f;
    }

    /**
     * applies each force to all the masses.
     * @param elapsedTime is time elapsed since last update
     * @param bounds of the simulation
     * @param masses is the list of all masses
     */
    public void update(Double elapsedTime, Dimension bounds, List<Mass> masses) {
        updateCentermass(masses);
        for (Force f : forces.values()) {
            f.applyForcesToMasses(masses, bounds);
        }
    }

    /**
     * updates the location of the center of mass based on
     *      mass locations.
     * @param masses is the list of all masses
     */
    public void updateCentermass(List<Mass> masses) {
        double totalMass = 0.0;
        double xCoordSum = 0.0;
        double yCoordSum = 0.0;
        for (Mass m : masses) {
            totalMass += m.getMass();
            xCoordSum += m.getX() * m.getMass();
            yCoordSum += m.getY() * m.getMass();
        }
        double xCM = xCoordSum / totalMass;
        double yCM = yCoordSum / totalMass;
        ((CenterOfMassForce) forces.get("centermass")).
            updateCenterOfMassLocation(new Location(xCM, yCM));
    }


    /**
     * Toggles specified force on/off.
     * @param forceName is the force identifier
     */
    public void setForceToggle(String forceName) {
        Force f = forces.get(forceName);
        if (f != null) {
            f.switchToggle();
        }
    }

    /**
     * sets the gravity.
     * @param g is new gravity vector
     */
    public void setGravity(Vector g) {
        GravityForce grav = new GravityForce(g);
        forces.put(GRAVITY_NAME, grav);
    }

    /**
     * sets the viscosity.
     * @param v is new viscosity value
     */
    public void setViscosity(double v) {
        Viscosity viscosity = new Viscosity(v);
        forces.put(VISCOSITY_NAME, viscosity);
    }

    /**
     * sets the center of mass array.
     * @param magnitude is the magnitude of the force
     * @param exponent is the exponent of the force
     * @param loc is the location of the cm
     */
    public void setCenterMass(double magnitude, double exponent, Location loc) {
        CenterOfMassForce centermass =
                new CenterOfMassForce(magnitude, exponent, loc);
        forces.put(CENTERMASS_NAME, centermass);
    }

    /**
     * sets the force of the desired wall.
     * @param wallNumber is the desired wall number
     * @param magnitude is the magnitude of the force
     * @param exponent is the exponent of the force
     */
    public void setWallsForce(int wallNumber, double magnitude, double exponent) {
        WallForce w;
        switch (wallNumber) {
            case TOP_WALL_ID:    w = new TopWallForce(magnitude, exponent);
                                 forces.put(TOPWALL_NAME, w);
                                 break;
            case RIGHT_WALL_ID:  w = new RightWallForce(magnitude, exponent);
                                 forces.put(RIGHTWALL_NAME, w);
                                 break;
            case BOTTOM_WALL_ID: w = new BottomWallForce(magnitude, exponent);
                                 forces.put(BOTTOMWALL_NAME, w);
                                 break;
            case LEFT_WALL_ID:   w = new LeftWallForce(magnitude, exponent);
                                 forces.put(LEFTWALL_NAME, w);
                                 break;
            default:             w = null;
                                 break;
        }
    }
}
